/**
 * 
 */
package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.netgame;

import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.filehandlers.PlayerFileHandler;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.Character;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.items.ItemFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.StatsFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.BattlePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.SimplePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Map;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.MapManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.GenericEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.exceptions.InvalidFileException;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

/**
 * @author Massimo Canonaco
 *
 */
public class BoomerangProtocol 
{

	/** */
	static public final String ADD_ME = "addme";
	
	/** */
	static public final String REMOVE_ME ="removeme";
	
	/** */
	static public final String NO_KEY = "nullkey";
	
	/** */
	private boolean youAreServerNow;
	
	/** */
	private Lock lock;
//	
//	/** */
//	private Condition condition;
//	
//	/** */
//	private boolean enemyOff;
//	
//	/** */
//	private boolean mapOff;
	
	/**
	 * @throws IOException  */
	static public void sendFile(File file, final Socket socket) throws IOException
	{
		DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

		// Send the length of file.
		dos.writeLong(file.length());

		// Send the file.
		byte[] bytes = new byte[8*1024];
		FileInputStream fis = new FileInputStream(file);
		int len;
		while((len = fis.read(bytes))>0) 
		{
		    dos.write(bytes, 0, len);
		}
		fis.close();
		dos.flush();
		dos.close();
	}
	
	static public void receiveFile(final String destination, final Socket socket) throws IOException
	{
		DataInputStream dis = new DataInputStream(socket.getInputStream());
		long lengthRemaining = dis.readLong();
		FileOutputStream fos = new FileOutputStream(destination);
		int len;
		byte[] bytes = new byte[8*1024];
		while(lengthRemaining > 0 
		   && (len = dis.read(bytes,0, (int) Math.min(bytes.length, lengthRemaining))) > 0) {
		      fos.write(bytes, 0, len);
		      lengthRemaining -= len;
		}
		fos.close();
		dis.close();
	}
	
	/** */
	private String serverIp;
	
	/** */
	private int serverPort;

	/** */
	private Socket connectionSocket;
	
	/** */
	private PrintWriter writer;
	
	/** */
	private BufferedReader reader;
	/** */
	public BoomerangProtocol(final String serverIp, final int serverPort)
	{
		this.serverIp = serverIp;
		this.serverPort = serverPort;
		
		lock = new ReentrantLock();
//		
//		condition = lock.newCondition();
//		
//		enemyOff = true;
//		
//		mapOff = true;
	}
	
	/**
	 * @throws IOException 
	 * @throws UnknownHostException  */
	public String sendState(final String message, final String key) throws UnknownHostException, IOException 
	{
		if(connectionSocket == null)
		{
			connectionSocket = new Socket(serverIp, serverPort);
			writer = new PrintWriter(connectionSocket.getOutputStream(),true);
			reader = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
		}
		
		
		writer.println(message);
		
		writer.println(key);
		String outerMessage = reader.readLine();

		return outerMessage;
	}
	
	/**
	 * @throws IOException 
	 * @throws UnknownHostException  */
	public String sendState_sh(final String message, final String key) throws UnknownHostException, IOException 
	{
//		if(connectionSocket == null)
//		{
			Socket connectionSocket = new Socket(serverIp, serverPort);
//		}
		PrintWriter writer = new PrintWriter(connectionSocket.getOutputStream(),true);
		BufferedReader reader = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
		
		writer.println(message);
		writer.println(key);
		String outerMessage = reader.readLine();
		writer.close();
		reader.close();
		connectionSocket.close();
		
		return outerMessage;
	}
	
	/**
	 * @throws IOException 
	 * @throws UnknownHostException  
	 * @throws InvalidFileException */
	public Map mapCheck(MapManager mapManager) throws UnknownHostException, IOException
	{
//		lock.lock();
		if(connectionSocket == null)
		{
			connectionSocket = new Socket(serverIp, serverPort);
			writer = new PrintWriter(connectionSocket.getOutputStream(),true);
			reader = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
		}
		
		boolean load = false;
		Map map = null;
		String mapName = reader.readLine();
//		System.out.println("Si combatte su: "+mapName);
		try 
		{
			map = mapManager.loadMap(mapName);
			//		writer.println(map.);
			writer.println(map.getSalt());
			load = !reader.readLine().equalsIgnoreCase("ok");
			
		} catch (InvalidFileException e) 
		{
			load = true;
		}
		
		if(load)
		{
//			System.out.println("mappa non valida, la ricevo dal server");
			receiveFile(mapManager.getFolder()+File.separator+mapName, connectionSocket);
			try {
				map = mapManager.loadMap(mapName);
			} catch (InvalidFileException e) 
			{
				// It's impossible, we're just loaded the map few seconds ago.
				e.printStackTrace();
			}
		}
//		mapOff = false;
//		lock.unlock();
		return map;
	}
	
	
	/**
	 * @throws IOException 
	 * @throws UnknownHostException  */
	public WaitingPlayerInformationBean receiveEnemyConnectionInformations() throws UnknownHostException, IOException
	{
		if(connectionSocket == null)
		{
			connectionSocket = new Socket(serverIp, serverPort);
			writer = new PrintWriter(connectionSocket.getOutputStream(),true);
			reader = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
		}
		
		WaitingPlayerInformationBean bean = null;
		
//		boolean server = reader.readLine().equalsIgnoreCase("true");
		youAreServerNow = reader.readLine().equalsIgnoreCase("true");
		int yourPort = Integer.parseInt(reader.readLine()), enemyPort = Integer.parseInt(reader.readLine());
		String enemyAddress = reader.readLine();
		System.out.println("PORTA NEMICO : "+enemyPort+" (la tua invece: )"+yourPort+" SEI IL SERVER: "+youAreServerNow);
		// Closing all the currents connections.
		connectionSocket.setReuseAddress(true);
		end();
		
		// Opening new connections for the "match" part.
		// If main server said you must be the server for the match.
		if(youAreServerNow)
		{
			boolean close = true;
			do
			{
				try
				{
					ServerSocket ss = new ServerSocket(yourPort);
					Socket s = ss.accept();
					// Be the server.
					bean = new WaitingPlayerInformationBean(0, s);
					close = false;
				}catch(BindException e)
				{
					close = true;
				}
			}while(close);
		}
		// Else connect your enemy (server).
		else
		{
			boolean notConnected = true;
			Socket socket = null;
			// While enemy is not ready for being the server.
			while(notConnected)
			{
				// Try to establish the connection.
				try
				{
				socket = new Socket(enemyAddress, enemyPort);
				notConnected = (socket != null) ? false : true;
				}catch(ConnectException e)
				{
					notConnected = true;
				}
			}
			bean = new WaitingPlayerInformationBean(0, socket);
		}
		
		// Update socket informations.
		connectionSocket = bean.getConnectionSocket();
		System.out.println("APERTE NUOVE CONNESSIONI CON IL NEMICO "+connectionSocket.getRemoteSocketAddress()+" fai da server "+youAreServerNow);
		writer = new PrintWriter(bean.getConnectionSocket().getOutputStream(),true);
		reader = new BufferedReader(new InputStreamReader(bean.getConnectionSocket().getInputStream()));
		return bean;
	}
	/**
	 * @throws IOException  */
	public void end() throws IOException
	{
		if(connectionSocket != null)
		{
			System.out.println("STATO PRIMA DELLE CHIAMATE DI CHIUSURA ( "+connectionSocket.isClosed()+" "+connectionSocket.isConnected()+" )");
			writer.close();
			reader.close();
			
			connectionSocket.close();
			System.out.println("STATO DOPO DELLE CHIAMATE DI CHIUSURA( "+connectionSocket.isClosed()+" "+connectionSocket.isConnected()+" )");
		}
		else
		{
			System.out.println("END OFF");
		}
	}
	
	/** */
	@SuppressWarnings("unchecked")
	public void sendPlayerInformations(SimplePlayer player)
	{ 
		JSONObject jsonPlayer = new JSONObject();
		jsonPlayer.put("name", player.getNickname());
		
		JSONArray jsonCharacterArray = new JSONArray();

		for (Character character : player.getCharacters()) 
		{
			JSONObject jCharacter = new JSONObject();
			jCharacter.put("cName", character.getName());
			jCharacter.put("cLevel", character.getLevel());
			jCharacter.put("cRole", character.getStats().getName());
			jsonCharacterArray.add(jCharacter);
			
			JSONArray jItem = new JSONArray();
			for ( String itemName : character.getInventory().getItemNames()) 
			{
				jItem.add(itemName);
			}
			jCharacter.put("cItems", jItem);
//			if(jItem.isEmpty())
		}
		jsonPlayer.put("warriors", jsonCharacterArray);
		writer.println(jsonPlayer.toJSONString());
	}

	public BattlePlayer receivePlayerInformations(StatsFactory factory) 
	{
		BattlePlayer enemy = new BattlePlayer();
		ItemFactory itmf = ItemFactory.getInstance();
		System.out.println("pre caricamento avversario");
		try 
		{
			String r = reader.readLine();
			System.out.println(r);
			JSONObject jEnemy = (JSONObject) JSONValue.parse(r);
			enemy.setNickname((String)jEnemy.get("name"));
			System.out.println("giochi contro: "+enemy.getNickname());
			JSONArray characters = (JSONArray) jEnemy.get("warriors");
			for (Object object : characters) 
			{
				JSONObject jChar = (JSONObject) object;
				Character character = new Character(((Long)jChar.get("cLevel")).intValue(), 0, enemy);
				character.setName((String)jChar.get("cName"));
				character.setStats(factory.levellingRole((String)jChar.get("cRole"), character.getLevel()));
				
				JSONArray items = (JSONArray) jChar.get("cItems");
				for (Object object2 : items) 
				{
//					String itemName = (String)object2;
					character.getStats().getCharacteristics().sum(itmf.getItem((String)object2), true);
				}
				enemy.addCharacter(character);
				System.out.println("ha scelto "+character.getName());
			}
		} catch (IOException e) 
		{
			e.printStackTrace();
		}
		return enemy;
	}

	public void sendEvent(GenericEvent event) 
	{
		System.out.println(event.getAsJSONObject().toJSONString()+"!!");
//		System.out.println(event.getAsJSONObject().toString()+"!!");
//		System.out.println();
		writer.println(event.getAsJSONObject().toJSONString());
	}

	public JSONObject receiveEvent() throws IOException, SocketTimeoutException
	{
		return receiveEvent(0);
	}
	
	public JSONObject receiveEvent(int millisec) throws IOException, SocketTimeoutException
	{
		connectionSocket.setSoTimeout(millisec);
		return (JSONObject) JSONValue.parse(reader.readLine());
	}

	/**
	 * @return the youAreServerNow
	 */
	public boolean isYouAreServerNow() {
		return youAreServerNow;
	}

	public String receiveMessage() throws IOException 
	{
		String message = reader.readLine();
		
		return message;
	}
	
	public void sendMessage(String message)
	{
		writer.println(message);
	}
}
